#include "uBroadcast_UDP.h"

#include <stdlib.h>
#include <stdio.h>
#include <netdb.h>
#include <sys/socket.h>

ub_UDP * ub_udp_connect(const char id[6], int port) {
	ub_UDP * conn = (ub_UDP *) malloc(sizeof(ub_UDP));
	if (!conn) {
		return NULL;
	}
	strncpy(conn->id, id, 6);
	conn->port = port;
	conn->udpSocket = socket(PF_INET, SOCK_DGRAM, 0);
	if (conn->udpSocket < 0) {
		perror("Failed to connect");
		free(conn);
		return NULL;
	}

	int on = 1;
	if (setsockopt(conn->udpSocket, SOL_SOCKET, SO_BROADCAST, &on,
			sizeof(int))) {
		perror("Broadcast flag");
		close(conn->udpSocket);
		free(conn);
		return NULL;
	}
	if (setsockopt(conn->udpSocket, SOL_SOCKET, SO_REUSEADDR, &on,
			sizeof(int))) {
		perror("Reuse flag");
		close(conn->udpSocket);
		free(conn);
		return NULL;
	}

	struct sockaddr_in myAddr;
	myAddr.sin_family = AF_INET;
	myAddr.sin_port = htons(conn->port);
	myAddr.sin_addr.s_addr = htonl(INADDR_ANY);

	if (bind(conn->udpSocket, (struct sockaddr *) &myAddr, sizeof(myAddr))
			< 0) {
		perror("Bind failed");
		close(conn->udpSocket);
		free(conn);
		return NULL;
	}

	return conn;
}

void ub_udp_send(ub_UDP * conn, uMessage * msg) {
	if (!conn) {
		perror("NULL UDP connection");
		return;
	}

	struct hostent *host = gethostbyname("255.255.255.255");
	if (host->h_addrtype != AF_INET) {
		perror("Broadcast address");
		return;
	}
	in_addr_t broadcastAddress = *(host->h_addr_list[0]);

	struct sockaddr_in toAddr;
	toAddr.sin_family = AF_INET;
	toAddr.sin_port = htons(conn->port);
	toAddr.sin_addr.s_addr = broadcastAddress;

	char buffer[UB_BUFFER_SIZE + 6];
	memcpy(buffer, conn->id, 6);
	memcpy(buffer + 6, msg->buffer, UB_SIZE(msg) + 3);
	size_t size = ((size_t) UB_SIZE(msg)) + 3 /*checksum,size,type*/+ 6 /*id*/;

//	printf("> ");
//	unsigned int i = 0;
//	for (; i < size; i++) {
//		if (i && i % 8 == 0) {
//			printf("\n> ");
//		}
//		printf("0x%02.2x ", buffer[i] & 0x0ff);
//	}
//	printf("\n");
//	printf("Size of packet transmitted: %d\n", size);

	if (sendto(conn->udpSocket, buffer, size, 0, (struct sockaddr *) &toAddr,
			sizeof(toAddr)) < 0) {
		perror("Broadcast error");
		return;
	}
}

int ub_udp_recv(ub_UDP * conn, uMessage * msg) {
	if (!conn) {
		perror("NULL UDP connection");
		return -1;
	}

	char buffer[UB_BUFFER_SIZE + 6];
	int size = recv(conn->udpSocket, buffer, UB_BUFFER_SIZE + 6, 0);
	if (size < 0) {
		perror("Message size");
		return size;
	}

//	printf("> ");
//	unsigned int i = 0;
//	for (; i < size; i++) {
//		if (i && i % 8 == 0) {
//			printf("\n> ");
//		}
//		printf("0x%02.2x ", buffer[i] & 0x0ff);
//	}
//	printf("\n");
//
//	printf("Message received size: %d\n", size);
//	printf("Message received type: %d\n", buffer[6]);
//	printf("Message received real size: %d\n", buffer[7]);

	if (!memcmp(buffer, conn->id, 6)) {
		// My own message
//		perror("My own message");
		ub_clear(msg);
		return 1;
	} else {
		memcpy(msg->buffer, buffer + 6, size - 6);
	}
	msg->ptr = msg->buffer + UB_SIZE(msg) + 3;
	msg->checksum = *msg->ptr;
	msg->ptr++;

	if (!ub_check(msg)) {
		ub_clear(msg);
		// Don't care, just throw it away
		perror("Invalid message received");
		return -1;
	}
	return 0;
}

void ub_udp_disconnect(ub_UDP * conn) {
	if (conn) {
		close(conn->udpSocket);
		free(conn);
	}
}
